package com.woniuxy.portal.controller;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.RandomUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.woniuxy.common.consts.RedisConst;
import com.woniuxy.common.consts.UserConst;
import com.woniuxy.common.enums.UserStatus;
import com.woniuxy.common.enums.impl.BusinessCode;
import com.woniuxy.common.exception.Assert;
import com.woniuxy.portal.model.entity.User;
import com.woniuxy.portal.model.vo.LoginRequest;
import com.woniuxy.portal.model.vo.RegisterRequest;
import com.woniuxy.portal.model.vo.UserVo;
import com.woniuxy.portal.service.UserService;
import com.woniuxy.portal.utils.JwtUtil;
import com.woniuxy.portal.utils.SmsUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomStringUtils;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;

import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.time.Duration;
import java.util.*;
import java.util.concurrent.TimeUnit;

@CrossOrigin(origins = "*")
@Slf4j
@Api(tags = "用户接口")
@RestController
@RequestMapping("/user")
public class UserController {

    private final UserService userService;
    private final JwtUtil jwtUtil;
    private final StringRedisTemplate stringRedisTemplate;
    private final SmsUtil smsUtil;
    private final JavaMailSender javaMailSender;
    private final RabbitTemplate rabbitTemplate;

    @Autowired
    public UserController(UserService userService, JwtUtil jwtUtil,
                          StringRedisTemplate stringRedisTemplate, SmsUtil smsUtil,
                          JavaMailSender javaMailSender, RabbitTemplate rabbitTemplate) {
        this.userService = userService;
        this.jwtUtil = jwtUtil;
        this.stringRedisTemplate = stringRedisTemplate;
        this.smsUtil = smsUtil;
        this.javaMailSender = javaMailSender;
        this.rabbitTemplate = rabbitTemplate;
    }

    /**
     * 用户登录接口
     *
     * @param loginRequest 登录请求参数，包含用户名和密码等信息
     * @param request      HTTP请求对象，用于获取客户端信息
     * @return ResponseEntity<?> 登录成功时返回包含访问令牌和刷新令牌的响应，登录失败时返回相应的错误信息
     */
    @ApiOperation("用户登录")
    @PostMapping("/login")
    public ResponseEntity<?> login(@RequestBody LoginRequest loginRequest, HttpServletRequest request) {
        // 调用userService的login方法进行用户登录验证
        ResponseEntity<?> resp = userService.login(loginRequest);
        // 登录成功时创建并返回访问令牌
        if (resp.getStatusCode() == HttpStatus.OK) {
            User user = (User) resp.getBody();
            // 创建token
            if (user != null) {
                return createTokens(user, request);
            }
        }
        return resp;
    }

    /**
     * 为用户创建访问令牌并返回响应
     *
     * @param user    用户对象，用于生成token和返回用户信息
     * @param request HTTP请求对象，用于获取客户端IP地址
     * @return ResponseEntity<?> 包含用户信息和token的响应实体，token通过响应头返回
     */
    private ResponseEntity<?> createTokens(User user, HttpServletRequest request) {
        // 构造token载荷数据
        Map<String, Object> mapData = new HashMap<>();
        mapData.put(UserConst.USER_ID, user.getId());
        mapData.put(UserConst.USER_NAME, user.getAccount());

        // 添加客户端IP地址到token载荷中
        mapData.put("ip", jwtUtil.getRequestIP(request));

        // 生成token
        String token = jwtUtil.createToken(mapData);

        // 将token存储到redis中，设置8小时过期时间
        String key = RedisConst.USER_TOKEN_PREFIX + ":" + user.getId();
        stringRedisTemplate.opsForValue().set(key, token, Duration.ofHours(8));

        // 转换用户对象为VO对象
        UserVo uservo = BeanUtil.copyProperties(user, UserVo.class);

        // 构造响应，将token通过Authorization响应头返回给客户端
        return ResponseEntity.ok()
                .header("Authorization", token)
                // 告知浏览器可以访问Authorization响应头，否则访问不了
                .header("Access-Control-Expose-Headers", "Authorization")
                .body(uservo);
    }

    /**
     * 生成图片验证码接口
     * <p>
     * 该方法用于生成一个包含4个随机字符的图片验证码，并将验证码字符串存储到Redis中，有效期为10分钟。
     * 同时将生成的图片以Base64编码格式返回给前端，便于在网页中直接展示。
     * 返回数据包括Redis中的key和图片的Base64编码。
     *
     * @return ResponseEntity 包含验证码key和图片Base64编码的响应体
     * @throws IOException 当图片写入输出流失败时抛出
     */
    @ApiOperation("生成图片验证码")
    @GetMapping("/captcha")
    public ResponseEntity<Map<String, String>> generateCaptcha() throws IOException {
        //2) 创建缓存图片：指定宽
        int width = 110, height = 45;
        //参数3：图片的类型，使用RGB类型
        BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        //3) 获取画笔对象
        Graphics graphics = image.getGraphics();
        //4) 设置画笔颜色，并且填充矩形区域
        graphics.setColor(Color.WHITE);
        graphics.fillRect(0, 0, width, height);
        //创建一个StringBuilder对象
        StringBuilder sb = new StringBuilder();
        //5) 从字符数组中随机得到字符
        String characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
        char[] arr = characters.toCharArray();
        Random random = new Random();
        for (int i = 0; i < 4; i++) {
            //随机从0到长度之间去取索引
            int index = random.nextInt(arr.length);
            //得到1个字符
            char c = arr[index];
            sb.append(c);
            //6) 设置字体，大小为22，设置字的颜色随机
            graphics.setFont(new Font(null, Font.BOLD + Font.ITALIC, 22));
            graphics.setColor(getColor());
            //7) 将每个字符画到图片，位置：5+(i*23), 25
            //将字符转成字符串
            graphics.drawString(String.valueOf(c), 5 + (i * 23), 25);
        }
        //8) 画干扰线8条线，线的位置是随机的，x范围在width之中，y的范围在height之中。
        for (int i = 0; i < 10; i++) {
            graphics.setColor(getColor());
            int x1 = random.nextInt(width);
            int y1 = random.nextInt(height);
            int x2 = random.nextInt(width);
            int y2 = random.nextInt(height);
            graphics.drawLine(x1, y1, x2, y2);
        }
        //9) 将缓存的图片输出到响应输出流中
        //参数1：要输出的缓存图片，参数2：格式，参数3：输出到哪个流中
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        ImageIO.write(image, "jpeg", outputStream);
        //随机生成一个key，前面加上code::
        String key = RedisConst.USER_VALIDATE_CODE + ":" + UUID.randomUUID().toString().replace("-", "");
        //得到验证码，将生成的字符串放到Redis中，键随机生成，10分钟过期
        stringRedisTemplate.opsForValue().set(key, sb.toString(), Duration.ofMinutes(10));
        log.info("生成的验证码为：{}，Redis中的key是：{}", sb, key);
        Base64.Encoder encoder = Base64.getEncoder();
        String base64Img = "data:image/jpeg;base64," + encoder.encodeToString(outputStream.toByteArray());
        //返回的数据包含验证码的key和图片
        Map<String, String> data = new HashMap<>();
        data.put("key", key);  //redis中键
        data.put("base64Img", base64Img);  //图片
        //返回key和图片的base64的编码
        return ResponseEntity.ok(data);
    }

    /**
     * 随机获取一个颜色对象
     * 该方法通过随机生成红、绿、蓝三个分量的值来创建一个随机颜色
     * 每个颜色分量的取值范围都是0-255
     *
     * @return Color 随机生成的颜色对象
     */
    private Color getColor() {
        Random random = new Random();
        // 随机生成红、绿、蓝三个颜色分量的值
        int r = random.nextInt(256);
        int g = random.nextInt(256);
        int b = random.nextInt(256);
        return new Color(r, g, b);
    }

    /**
     * 判断用户是否登录
     *
     * @param authorization 用户认证令牌，从请求头Authorization中获取
     * @return ResponseEntity<?> 响应实体，包含用户信息或错误信息
     */
    @ApiOperation("判断用户是否登录")
    @GetMapping("/isLogged")
    public ResponseEntity<?> isLogged(@RequestHeader(required = false, name = "Authorization") String authorization) {
        log.info("用户请求头中携带的Token是：{}", authorization);
        // 验证用户是否登录（无内容抛出异常）
        Assert.error(!StringUtils.hasText(authorization), BusinessCode.USER_NOT_LOGGED);
        // 判断token是否有效
        boolean isValid = jwtUtil.validateToken(authorization);
        Assert.error(!isValid, BusinessCode.USER_NOT_LOGGED);
        // 获取用户ID
        Long userId = Long.valueOf(jwtUtil.getValue(authorization, UserConst.USER_ID));
        // 调用业务层查询用户是否存在
        UserVo userVo = userService.findById(userId);
        Assert.error(userVo == null, BusinessCode.USER_NOT_EXIST);
        return ResponseEntity.ok(userVo);
    }

    /**
     * 用户退出登录
     * 该方法通过解析token获取用户ID，删除Redis中存储的用户token，并清除HTTP响应缓存
     *
     * @param token    用户认证令牌，从Authorization请求头中获取
     * @param response HTTP响应对象，用于设置缓存控制头
     * @return ResponseEntity<String> 返回退出成功信息
     */
    @ApiOperation("用户退出登录")
    @DeleteMapping("/logout")
    public ResponseEntity<String> logout(@RequestHeader(required = false, name = "Authorization") String token, HttpServletResponse response) {
        // 通过token获取用户ID
        String userId = jwtUtil.getValue(token, UserConst.USER_ID);
        // 删除redis中的token
        stringRedisTemplate.delete(RedisConst.USER_TOKEN_PREFIX + ":" + userId);
        // 清除缓存
        response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
        response.setHeader("Pragma", "no-cache");
        response.setHeader("Expires", "0");
        return ResponseEntity.ok("退出成功");
    }

    /**
     * 发送短信验证码
     * 该方法通过手机号生成随机验证码，调用短信服务发送验证码，并将验证码存储到Redis中
     *
     * @param telephone 接收短信的手机号码
     * @return ResponseEntity<?> 包含发送结果的响应实体
     */
    @ApiOperation("手机号发送短信")
    @GetMapping("/sendSms/{telephone}")
    public ResponseEntity<?> sendSms(@PathVariable String telephone) {
        // 1. 随机生成验证码，随机生成4个数字
        String code = RandomStringUtils.randomNumeric(4);
        log.info("生成的验证码为：{}", code);

        // 2. 调用阿里云的短信服务API完成发送短信
        // 给手机号发送短信，只需要测试一次，如果成功就没有必要每次发送
//        String result = smsUtil.sendSms(telephone, code);
//        Assert.error(!result.equals("OK"), BusinessCode.SMS_SEND_ERROR);
        String result = "OK";

        // 3. 将验证码保存到Redis中，设置10分钟过期
        stringRedisTemplate.opsForValue().set(
                RedisConst.SHORT_MESSAGE_SERVICE + ":" +
                        telephone, code, Duration.ofMinutes(10));
        String response = "给手机号：" + maskPhoneNumber(telephone) + "成功发送验证码";
        log.info("发送短信的结果为：{}", result);
        return ResponseEntity.ok(response);
    }

    /**
     * 对电话号码进行脱敏处理
     *
     * @param telephone 待脱敏的电话号码字符串
     * @return 脱敏后的电话号码，格式为前3位+****+后4位
     */
    private String maskPhoneNumber(String telephone) {
        // 将第4、5、6、7位替换为 *
        return telephone.substring(0, 3) + "****" + telephone.substring(7);
    }

    /**
     * 手机号 + 短信验证码登录：如果手机号不存在，就自动注册！自动登陆系统
     *
     * @param params  登录请求参数，包含手机号和验证码
     * @param request HTTP请求对象，用于获取客户端信息
     * @return ResponseEntity<?> 登录结果响应实体，包含token等登录信息
     */
    @ApiOperation("手机号登录")
    @PostMapping("/smsLogin")
    public ResponseEntity<?> smsLogin(@RequestBody LoginRequest params, HttpServletRequest request) {
        // 获取手机号
        String telephone = params.getTelephone();
        // 从redis获取验证码
        String redisCode = stringRedisTemplate.opsForValue().get(RedisConst.SHORT_MESSAGE_SERVICE + ":" + telephone);
        // 判断验证码是否过期
        Assert.error(redisCode, BusinessCode.INVALID_VERIFICATION);
        // 判断验证码是否正确
        String code = params.getCode();
        Assert.error(!code.equals(redisCode), BusinessCode.VERIFICATION_ERROR);
        // 删除验证码
        stringRedisTemplate.delete(RedisConst.SHORT_MESSAGE_SERVICE + ":" + telephone);
        // 根据手机查询用户信息
        User user = userService.getOne(Wrappers.lambdaQuery(User.class).eq(User::getMobile, telephone));
        // 如果用户存在，判断状态是否正常
        Assert.error(user != null && !user.getState().equals(UserStatus.ACTIVE.getKey()), BusinessCode.USER_FROZEN_ERROR);
        // 如果用户不存在，创建新用户
        if (Objects.isNull(user)) {
            //创建默认密码：123
            BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
            String encode = encoder.encode("123");
            user = new User();
            user.setMobile(telephone);   //手机号
            user.setPassword(encode);    //加密密码
            user.setAccount(telephone);  //账户名：手机号
            user.setNickname(telephone);
            user.setRealName(telephone);
            user.setState(UserStatus.ACTIVE.getKey()); //状态
            userService.save(user);
        }
        //不为空则登录成功，创建token并返回
        return createTokens(user, request);
    }

    /**
     * 发送邮件接口，用于向指定邮箱发送验证码
     * <p>
     * 该接口会先校验邮箱是否已注册，只有已注册的用户才能接收验证码。
     * 验证码为4位随机数字，有效期为15分钟，并通过Redis进行存储。
     * 邮件发送过程使用后台线程异步执行，以提升接口响应速度。
     *
     * @param email 接收验证码的邮箱地址
     * @return ResponseEntity<String> 响应结果，表示邮件是否发送成功
     */
    @ApiOperation("发送邮件接口")
    @GetMapping("/sendEmail")
    public ResponseEntity<?> sendEmail(String email) {
        //邮箱地址
        String from = "15396267506@163.com";

        //判断邮箱是否已经注册：已存在不允许注册，存在才可以发送
        User user = userService.getOne(
                Wrappers.lambdaQuery(User.class).select(User::getId).eq(User::getEmail, email));
//        Assert.error(user != null, BusinessCode.EMAIL_EXIST_ERROR);
        if (user != null) {
            return ResponseEntity.ok(Map.of("code", 400, "msg", "邮箱已经被注册"));
        }

        String text = RandomUtil.randomNumbers(4);
        String context = "注册的验证码是：" + text + ", 该验证码15分钟之内有效！";
/*
        //发送邮件
        SimpleMailMessage mail = new SimpleMailMessage();
        mail.setFrom(from); //发件箱
        mail.setTo(email);  //收件箱
        mail.setSubject("蜗牛书城：验证码"); //邮件主题
        mail.setText(context); //生成的验证码

        // 开启一个后台线程异步发送邮件，避免阻塞主线程
        new Thread(() -> {
            //发送成功能以后，测试的时候把这句话注释了，以免每次发送邮件，被拉黑
            //连接邮件服务器 smtp.163.com
            javaMailSender.send(mail);
            //模拟发送邮件
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            log.info("验证码{}，发送到{}", text, email);
        }).start();
*/
        // 发送邮件改成 rabbitMQ 发消息
        Map<String, Object> emailData = new HashMap<>();
        emailData.put("from", "15396267506@163.com");
//        emailData.put("to", "15396267506@163.com");
        emailData.put("to", email);
        emailData.put("subject", "蜗牛书城：注册验证码");
        emailData.put("content", context);
        rabbitTemplate.convertAndSend("email.queue", emailData);

        //将数据存储在Redis中,时间设置为15分钟，邮箱地址是键
        stringRedisTemplate.opsForValue().set(
                RedisConst.EMAIL_ADDRESS_PREFIX + ":" +
                        email, text, RedisConst.EMAIL_CODE_EXPIRE, TimeUnit.MINUTES);
//        return ResponseEntity.ok(BusinessCode.EMAIL_SEND_OK.getMessage());
        return ResponseEntity.ok(Map.of("code", 200, "msg", "邮箱发送成功"));
    }

    /**
     * 用户注册接口
     *
     * @param registerRequest 注册请求参数对象，包含用户注册所需的信息
     * @param request         HTTP请求对象，用于获取客户端信息
     * @return ResponseEntity<?> 返回注册结果，成功时返回token信息，失败时返回相应错误码
     */
    @ApiOperation("用户注册")
    @PostMapping("/register")
    public ResponseEntity<?> register(@RequestBody RegisterRequest registerRequest, HttpServletRequest request) {
        String key = RedisConst.EMAIL_ADDRESS_PREFIX + ":" + registerRequest.getEmail();
        //从Redis中获取验证码是否正确
        String redisCode = stringRedisTemplate.opsForValue().get(key);

        System.out.println("Redis key: " + key + ", code in redis: " + redisCode);
        System.out.println("User input code: " + registerRequest.getValidCode());

        //判断验证码是否过期
        Assert.error(redisCode == null, BusinessCode.INVALID_VERIFICATION);

        //判断验证码是否正确
        Assert.error(!redisCode.equals(registerRequest.getValidCode()), BusinessCode.VERIFICATION_ERROR);

        //校验用户信息是否重复
        int row = this.checkRegister("account", registerRequest.getAccount());
        Assert.error(row > 0, BusinessCode.USER_IS_EXIST);
        //判断手机是否重复
        row = this.checkRegister("mobile", registerRequest.getMobile());
        Assert.error(row > 0, BusinessCode.MOBILE_IS_EXIST);
        //校验邮箱的email
        row = this.checkRegister("email", registerRequest.getEmail());
        Assert.error(row > 0, BusinessCode.EMAIL_EXIST_ERROR);

        //创建用户对象
        User user = new User();
        //将注册信息复制到user对象中
        BeanUtils.copyProperties(registerRequest, user);
        //调用业务层注册
        userService.save(user);
        //默认已经登录
        return createTokens(user, request);
    }

    /**
     * 检查用户注册信息是否存在
     * 通过指定的列名和值来查询用户表中符合条件的记录数量
     *
     * @param columName 数据库列名
     * @param value     要查询的值
     * @return int 符合条件的用户记录数量
     */
    private int checkRegister(String columName, String value) {
        // 构造查询条件
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq(columName, value);
        // 返回符合条件的记录数量
        return userService.count(queryWrapper);
    }

}
